
#ifndef LIBFM_H
#define LIBFM_H

#include <filesystem>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <tuple>

#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>

using namespace std;

namespace libfm 
{

    namespace 
    {
        inline vector<string> getPathEntries(string path, bool checkDirs, bool checkFiles) {
            vector<string> entries;
            for (const auto &entry : filesystem::directory_iterator(path)) {
                if (
                    (checkFiles && checkDirs)
                    ||
                    (checkDirs && entry.is_directory())
                    || 
                    (checkFiles && !entry.is_directory())
                )
                    entries.push_back(entry.path());
            }
            return entries;
        }

        inline int convertBitesToType(long value, string dataType) {
            vector<string> dataTypes = {
                "b", "Kb", "Mb", "Gb", "Tb"
            };

            int power = 1000;
            int index = 0;

            while (dataTypes.size() > index && dataTypes[index] != dataType) {
                value /= power;
                index++;
            }
            return value;
        }

        inline tuple<int, string> convertBites(long value) {
            if (value < 1) return {0, "b"};
            int totalValue = value;
            int index = 0;

            vector<string> dataTypes = {
                "b", "Kb", "Mb", "Gb", "Tb"
            };

            string totalDataType = "";

            while (dataTypes.size() > index) {
                totalDataType = dataTypes[index];
                long tempValue = convertBitesToType(value, totalDataType);
                if (tempValue < 1) {
                    totalDataType = dataTypes[index-1];
                    break;
                }
                totalValue = tempValue;
                index++;
            }

            return {totalValue, totalDataType};
        }
    }

    inline vector<string> getEntries(string path) {
        return getPathEntries(path, true, true);
    }

    inline vector<string> getFiles(string path) {
        return getPathEntries(path, false, true);
    }

    inline vector<string> getDirectories(string path) {
        return getPathEntries(path, true, false);
    }

    inline string getHome() {
        string homedir;
        if ((homedir = getenv("HOME")).c_str() == NULL) {
            homedir = getpwuid(getuid())->pw_dir;
        }
        return homedir;
    }

    inline fstream makeFile(string path) {
        fstream file;
        file.open(path, ios::out);
        if (!file) {
            cout << "ERROR: Can't create file!" << endl;
        }
        return file;
    }

    inline void move(string from, string to) {
        filesystem::rename(from, to);
    }

    inline void copy(string from, string to) {
        filesystem::copy(from, to);
    }

    inline void rm(string path) {
        remove(path.c_str());
    }

    inline filesystem::file_time_type getFileLastWriteTime(string path) {
        return filesystem::last_write_time(path);
    }

    inline long getFileSizeInBites(string path) {
        return filesystem::file_size(path);
    }

    inline long getFileSizeByType(string path, string dataType) {
        return convertBitesToType(getFileSizeInBites(path), dataType);
    }

    inline tuple<int, string> getFileSize(string path) {
        return convertBites(getFileSizeInBites(path)); 
    }

    inline string readFile(string path) {
        ifstream myfile; myfile.open(path);
        string myline; string text;
        if (myfile.is_open()) {
            while (myfile) { // equivalent to myfile.good()
                getline(myfile, myline);
                text += myline + "\n";
            }
        } 
        return text;
    }

    inline string getFilename(string path) {
        return filesystem::path(path).filename();
    }

    inline string getDirname(string path) {
        return filesystem::path(path).parent_path();
    }

    inline string getFileExtension(string path) {
        return filesystem::path(path).extension();
    }

    inline bool isFileBin(string path) {
        FILE *fp; char out[1035];

        /* Open the command for reading. */
        string command = "file " + path;
        fp = popen(command.c_str(), "r");
        if (fp) {
            /* Read the output a line at a time - output it. */
            std::string totalOut = "";
            while (fgets(out, sizeof(out), fp) != NULL) {
                totalOut += out; 
            }
            /* close */
            pclose(fp);

            vector<string> textFormats = {
                "UTF", "ASCII", "JSON"
            };

            for (const auto &format : textFormats) {
                if (totalOut.find(format) != string::npos)
                    return false;
            }
        }
        return true;
    }

    inline void setCurrentPath(string path) {
        filesystem::current_path(path);
    }

    inline bool writeTextToFile(string path, string text) {
        ofstream file(path);
        if (file.is_open()) {
            file << text;
            file.close();
            return true;
        }
        return false;
    }

    inline bool appendTextToFile(string path, string text) {
        ofstream file(path, ios_base::app);
        if (file.is_open()) {
            file << text;
        }
        return false;
    }

}

#endif // LIBFM_H

